/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
package org.erossi.common.util;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Calendar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.erossi.common.exception.AppException;
import org.erossi.common.exception.SysException;

/**
 * 'AppCtx' class: classe singleton, con funzioni di utilità generale
 *
 * <p>La classe implementa il Singleton Pattern 
 *   <a href="http://en.wikipedia.org/wiki/Singleton_pattern">http://en.wikipedia.org/wiki/Singleton_pattern</a></p>
 *
 * @author erossi
 * @version Feb 9, 2007
 */
public class AppCtx {

  private static final long serialVersionUID = 1L;

  public static final String NULL_STRING = "{null}"; 
  public static final String DEFAULT_STACK_TRACE_MSG = "NO STACKTRACE AVAILABLE :("; 
  
  private static final Log log = LogFactory.getLog(AppCtx.class);
  
  /**
   * L'istanza della classe stessa
   */
  private static AppCtx istanza;
  
  /**
   * Crea una nuova istanza della classe
   * 
   * <p>
   *   <b>Il metodo non é accessibile :)</b> La classe deve essere creata
   *   attraverso il metodo {@link #getInst()}.
   * </p>
   */
  private AppCtx() {
  }

  /**
   * Ritorna l'istanza della classe.
   * 
   * <p>
   *   L'istanza della classe ritornata viene generata alla prima chiamata
   *   del metodo. Le chiamate successive ritornano sempre l'istanza così 
   *   generata.
   * </p>
   * 
   * <p>
   *   Il metodo é implementato in modo thread-safe.
   * </p> 
   * 
   * @return l'istanza della classe
   */
  public synchronized static AppCtx getInst() {

    if (istanza == null) {
      istanza = new AppCtx();
    }

    return istanza;

  }
  
  /**
   * Crea una rappresentazione sotto forma di stringa dello stacktrace
   * 
   * @param th un'oggetto {@link java.lang.Throwable} da rappresentare.
   * @return la rappresentazione in forma di stringa dell'oggetto passato.
   */
  private static String getStackTraceStr(Throwable th) {

    String rappresentazione = new String();

    if (th != null) {
      
      rappresentazione = th.getMessage() + "\n";
      
      try {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(baos);
        th.printStackTrace(pw);
        pw.flush();
        
        rappresentazione += baos.toString();

      } catch (Exception e) {

        rappresentazione += AppCtx.DEFAULT_STACK_TRACE_MSG;
        log.error("ERRORE NELLA CREAZIONE DELLO STACK TRACE: " + e.getMessage());

      }

    } else {
      
      rappresentazione += AppCtx.NULL_STRING;
      
    }
    
    return rappresentazione;

  }
  
  /**
   * Recupera una risorsa entro il <code>CLASSPATH</code>.
   * 
   * <p>
   *   I nomi dei file devono essere comprensivi del classpath, separato dal carattere "/". 
   *   Non é previsto un carattere <code>"/"</code> iniziale, poiché tutti i path sono considerati assoluti.
   * </p>
   * 
   * <p>
   *   I files <b>DEVONO</b> essere reperibili nel classpath.
   * </p>
   * 
   * @param nomeRisorsa il nome della risorsa
   * @return lo stream che rappresenta la risorsa
   * @throws SysException l'eccezione {@link org.erossi.common.exception.SysException} 
   *   viene lanciata se si verifica un errore nel caricamento del file <code>nomeRisorsa</code>,
   *   oppure se viene richiesta una risorsa <code>null</code>
   */
  public InputStream loadResource(String nomeRisorsa) throws SysException {
    
    InputStream is = null;
    
    if (nomeRisorsa == null) {
      
      SysException se = new SysException("RICHIESTA RISORSA NULLA: " + AppCtx.printDebug("nomeRisorsa", nomeRisorsa), new Exception());
      log.error(AppCtx.printDebug("se", se));
      throw se;  
      
    } else {
      
      is = AppCtx.getInst().getClass().getClassLoader().getResourceAsStream(nomeRisorsa);
      log.info("CARICAMENTO RISORSA: " + AppCtx.printDebug("nomeRisorsa", nomeRisorsa));
      
      if (is == null) {
        
        SysException se = new SysException("IMPOSSIBILE CARICARE LA RISORSA: " + AppCtx.printDebug("nomeRisorsa", nomeRisorsa), new Exception());
        log.error(AppCtx.printDebug("se", se));
        throw se;  
        
      }
      
    }
    
    return is;
    
  }
  
  /**
   * Ritorna una stringa custom che rappresenta l'oggetto.
   *   Da utilizzarsi per debugging/logging.
   * 
   * Crea una rappresentazione conveniente per oggetti complessi:
   * <ul>
   *   <li><code>null</code>
   *   <li>{@link java.util.Calendar}
   *   <li>{@link java.lang.Throwable}
   *   <li>Array of {@link java.lang.Character}
   *   <li>{@link java.lang.Class}
   *   <li>{@link java.lang.reflect.Method}
   * </ul>
   * 
   * @param nome il nome da utilizzare per identificare facilmente l'oggetto
   * @param oggetto l'oggetto di cui creare la rappresentazione
   * @return una rappresentazione stringa dell'oggetto.
   */
  @SuppressWarnings("unchecked")
  public static String printDebug(String nome, Object oggetto) {
    
    String risposta = new String();
    
    if (oggetto == null) {
      risposta = AppCtx.NULL_STRING;
    } else {
      
      if (oggetto instanceof Calendar) {
        try {
          risposta = String.format("%1$ta, %1$td %1$tb %1$tY, %1$tH:%1$tM %1$tS.%1$tL", oggetto);
        } catch (Exception e) {
          risposta = oggetto.toString();
        }
      } else {
        if (oggetto instanceof Throwable) {
          try {
            risposta = AppCtx.getStackTraceStr(((Exception) oggetto));
          } catch (Exception e) {
            risposta = oggetto.toString();
          }
        } else {
          if (oggetto instanceof char[]) {
            risposta = new String((char[]) oggetto);
          } else {
            if (oggetto instanceof Class) {
              risposta = oggetto.getClass().getName();
            } else {
              if (oggetto instanceof Method) {
                risposta = ((Method) oggetto).getName();
              } else {
                risposta = oggetto.toString();
              }
            }
          }
        }
      }
    }
      
    return nome + "=|" + risposta + "|\n"; 
   
  }
  
  /**
   * Ritorna una stringa custom che rappresenta un array di oggetti.
   *   Da utilizzarsi per debugging/logging.
   *   
   * Crea una rappresentazione conveniente per oggetti complessi:
   * <ul>
   *   <li>Array of {@link java.lang.Object}
   * </ul>
   * 
   * <p>
   *   Utilizza, per il debugging del singolo elemento dell'array, il 
   *   metodo {@link #printDebug(String, Object)}.
   * </p>
   * 
   * @param nome il nome da utilizzare per identificare facilmente l'oggetto
   * @param oggetto l'oggetto di cui creare la rappresentazione
   * @return una rappresentazione stringa dell'oggetto.
   */
  public static String printDebug(String nome, Object[] oggetto) {
    
    String risposta = new String();
    
    if (oggetto == null) {
      risposta = AppCtx.NULL_STRING;
    } else {
      for (int i = 0; i < oggetto.length; i++) {
        risposta += AppCtx.printDebug("Array element #" + i, oggetto[i]);
      }
    }
    
    return nome + "=|" + risposta + "|\n"; 
    
  }
  
  /**
   * Effettua un padding da sinistra di una stringa, 
   *   se necessario.
   * 
   * @param stringa la stringa di cui effettuare il padding
   * @param lunghezza la lunghezza desiderata
   * @param riempimento la stringa/carattere di riempimento
   * @return una stringa della lunghezza desiderata, riempita, se necessario
   *   con il carattere di riempimento
   */
  public static String lpad(String stringa, Integer lunghezza, String riempimento) {
    
    String risposta = stringa;
    
    if (risposta == null) {
      
      AppException ae = new AppException("PADDING DI STRINGA NULLA: " + AppCtx.printDebug("stringa", stringa), new Exception());
      log.error(AppCtx.printDebug("ae", ae));
      throw ae;  
      
    } else {
    
      if (stringa.length() < lunghezza) {
        
        while (risposta.length() < lunghezza) {
          risposta = riempimento + risposta;
        }
        
      }
    
    }
    
    return risposta;
    
  }
  
  /**
   * Ritorna una rappresentazione sotto forma di 
   *   stringa dell'oggetto.
   * 
   * @return una stringa che rappresenta l'oggetto
   */
  public String toString() {

    String toString = new String();
    toString = this.getClass().getName();
    
    return toString;

  }
  
}
